package cn.steam.foo.util.lock;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author zhang jintao on 2022/05/19
 **/
@Aspect
@Component
@Slf4j
public class KLockAspect {

    @Resource(name = "redissonLock")
    Lock lock;

    @Around("@annotation(kLock)")
    public Object execute(ProceedingJoinPoint pjp, KLock kLock) throws Throwable {
        String key = parseKey(pjp, kLock);
        if (!lock.tryLock(key)) {
            log.error("获取分布式锁失败,请稍后重试,key:{}", key);
            throw new RuntimeException("获取分布式锁失败,请稍后重试");
        }
        try {
            return pjp.proceed();
        } finally {
            lock.unlock(key);
        }

    }


    /**
     * 解析出锁🔐的key
     */
    private String parseKey(ProceedingJoinPoint pjp, KLock kLock) {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        if (paraNameArr == null) {
            throw new IllegalArgumentException("KLock 不能作用于无参数方法");
        }
        Object[] args = pjp.getArgs();
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }

        String key = StringUtils.isEmpty(kLock.key()) ? Arrays.toString(args)
                : parser.parseExpression(kLock.key()).getValue(context, String.class);

        String prefix = StringUtils.isEmpty(kLock.prefix()) ? method.getName() + ":" : kLock.prefix();

        return prefix + key;
    }

}
